// 一个交互操作导致死锁的例子。
#include <muduo/base/Mutex.h>
#include <muduo/base/Thread.h>
#include <set>
#include <stdio.h>
#include <unistd.h>

using muduo::MutexLock;
using muduo::MutexLockGuard;

class Request;

class Inventory {
public:
	void Add(Request *req) {
		MutexLockGuard lock(_mutex);
		_requets.insert(req);
	}

	void Remove(Request *req) __attribute__((noinline)) {
		MutexLockGuard lock(_mutex);
		_requets.erase(req);
	}

	// 先 lock Inventory 再 lock Request 。
	void PrintAll() const;

private:
	mutable MutexLock _mutex;
	std::set<Request*> _requets;
};

Inventory g_inventory;

class Request {
public:
	// 先 lock Request 再 lock Inventory 。
	~Request() {
		MutexLockGuard lock(_mutex);
		sleep(1);
		g_inventory.Remove(this);
	}

	void Process() {
		MutexLockGuard lock(_mutex);
		g_inventory.Add(this);
	}

	void Print() const __attribute__((noinline)) {
		MutexLockGuard lock(_mutex);
	}

private:
	mutable MutexLock _mutex;
};

void
Inventory::PrintAll() const {
	MutexLockGuard lock(_mutex);
	sleep(1);
	for (std::set<Request*>::const_iterator it = _requets.begin();
		 it != _requets.end();
		 ++it) {
		(*it)->Print();
	}
	printf("Inventory::PrintAll unlocked\n");
}

/*
void 
Inventory::printAll() const
{
	std::set<Request*> requests
	{
		muduo::MutexLockGuard lock(mutex_);
		requests = requests_;
	}
	for (std::set<Request*>::const_iterator it = requests.begin();
	     it != requests.end();
	     ++it) {
		(*it)->print();
	}
}
*/

void 
ThreadFunc() {
	Request* req = new Request;
	req->Process();
	delete req;
}

int 
main() {
	// main 线程先调用 PrintAll 再调用 Print ，
	// 子线程先调用 ~Request 再调用 Remove ，
	// 这两个调用序列对两个 Mutex 加锁顺序正好相反，于是造成了经典的死锁。
	muduo::Thread thread(ThreadFunc);
	thread.start();
	// 子线程中 lock Request 然后等待 1 秒，而主线程等待了 0.5 秒，此时 lock invevtory 。
	usleep(500 * 1000);
	g_inventory.PrintAll();
	thread.join();
}

/*
首先需要确认当前会话的 ulimit –c ，若为 0 则不会产生对应的 coredump ，
需要进行设置， ulimit -c unlimited （可以产生 coredump 且不受大小限制）。重启后需要重新设置。

要想让死锁的进程产生 coredump 可以使用 kill -s QUIT pid 把 pid 替换成需要 kill 的进程 id 即可，
然后 gdb 可执行文件 core 并使用命令 thread apply all bt 查看所有线程堆栈，来查看死锁问题。
**/